﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Windows.Forms;
using ZackFlame.IronBrainFuck;
using ZackFlame.SimpleBrainFuck.TextResources;

namespace ZackFlame.SimpleBrainFuck.Controls
{
    public partial class BrainFuckInteractiveConsole : UserControl
    {
        const string StartText = "IronBrainFuck Interactive Console";

        BrainFuckMachine brainFuckMachine = new BrainFuckJIT();
        Thread brainFuckThread;
        bool running = false;
        bool inputEnabled = true;

        InteractiveConsoleReader reader;
        InteractiveConsoleWriter writer;

        public BrainFuckInteractiveConsole()
        {
            InitializeComponent();
            this.Disposed += BrainFuckInteractiveConsole_Disposed;
            
            writer = new InteractiveConsoleWriter(textBoxConsole);
            reader = new InteractiveConsoleReader(commandBox);

            reader.WaitInput += new EventHandler(reader_WaitInput);
            reader.GotInput += new EventHandler(reader_GotInput);
            reader.LineReaded += new EventHandler<ReadEventArgs>(reader_LineReaded);

            brainFuckMachine.Input = reader;
            brainFuckMachine.Output = writer;

            Reset();
        }

        public void Reset()
        {
            if (running)
            {
                brainFuckThread.Abort();
                running = false;
                writer.WriteToTextBox = false;
            }

            textBoxConsole.Clear();
            textBoxConsole.AppendText(StartText + Environment.NewLine);
            reader.ClearInput();

            EnableCommandBox();
            commandBox.Focus();

            brainFuckMachine.Reset();
        }

        private void EnableCommandBox()
        {
            reader.DoWaitForInput = false;

            if (running)
            {
                if (reader.HasBufferedInput)
                    return;

                reader.DoWaitForInput = true;
                commandBox.BackColor = Color.LightGreen;
                commandBox.Focus();
            }
            else
            {
                commandBox.BackColor = SystemColors.Window;
            }

            inputEnabled = true;
        }

        private void DisableCommandBox()
        {
            reader.DoWaitForInput = false;
            commandBox.BackColor = SystemColors.ControlDark;
            inputEnabled = false;
        }

        private void reader_WaitInput(object sender, EventArgs e)
        {
            EnableCommandBox();
        }

        private void reader_GotInput(object sender, EventArgs e)
        {
            DisableCommandBox();
        }

        private void reader_LineReaded(object sender, ReadEventArgs e)
        {
            textBoxConsole.AppendText(
                e.ReadedText.Replace("\n", Environment.NewLine));
        }

        private void commandBox_KeyDown(object sender, KeyEventArgs e)
        {
            if (running)
            {
                if (!inputEnabled)
                    e.SuppressKeyPress = true;

                return;
            }

            if (e.KeyData == Keys.Enter)
            {
                e.Handled = true;
                e.SuppressKeyPress = true;

                string currentCommand = commandBox.Text;
                RunCode(currentCommand);

                for (int i = 0; i < commandBox.Items.Count; i++)
                {
                    string command = (string)commandBox.Items[i];
                    if (string.Equals(command, currentCommand,
                        StringComparison.Ordinal))
                    {
                        commandBox.Items.RemoveAt(i);
                        break;
                    }
                }

                if (currentCommand != string.Empty)
                    commandBox.Items.Insert(0, currentCommand);

                commandBox.Text = string.Empty;
            }
        }

        public void RunCode(string brainFuckCode)
        {
            if (!running)
            {
                running = true;
                DisableCommandBox();
                writer.WriteToTextBox = true;

                brainFuckThread = new Thread(() =>
                {
                    try
                    {
                        brainFuckMachine.Execute(brainFuckCode);
                    }
                    catch (InvalidProgramException ex)
                    {
                        writer.WriteLine("Error: " + ex.Message);
                    }
                    catch (FormatException)
                    {
                        writer.WriteLine(RuntimeErrors.RunCode_InvalidSpecialInput);
                    }
                    catch (OverflowException)
                    {
                        writer.WriteLine(RuntimeErrors.RunCode_TooBigSpecialInput);
                    }

                    this.Invoke(new Action(() =>
                    {
                        running = false;
                        EnableCommandBox();
                    }));
                });

                brainFuckThread.IsBackground = true;
                brainFuckThread.Start();
            }
        }

        private void BrainFuckInteractiveConsole_Disposed(object sender, EventArgs e)
        {
            reader.Dispose();
            writer.Dispose();
        }
    }
}
